namespace Zen.Data.Extensions
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Linq.Expressions;
	using System.Text;

	/// <summary>
	/// ContainsExtension adds a LINQ to Entities capable contains query
	/// expression to the IQueryable interface.
	/// </summary>
	public static class ContainsExtension
	{
		/// <summary>
		/// Returns an IQueryable of elements that contain one or more values
		/// in the specified collection.
		/// </summary>
		/// <typeparam name="TElement">The type of the element.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="query">The query.</param>
		/// <param name="valueSelector">The value selector.</param>
		/// <param name="values">The values.</param>
		/// <returns></returns>
		public static IQueryable<TElement> ContainsEx<TElement, TValue>(
			this IQueryable<TElement> query,
			Expression<Func<TElement, TValue>> valueSelector,
			IEnumerable<TValue> values)
		{
			return query.Where<TElement>(
				BuildContainsExpression<TElement, TValue>(
				valueSelector, values));
		}

		/// <summary>
		/// Builds the contains expression.
		/// </summary>
		/// <typeparam name="TElement">The type of the element.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="valueSelector">The value selector.</param>
		/// <param name="values">The values.</param>
		/// <returns></returns>
		public static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
			Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
		{
			if (null == valueSelector)
			{
				throw new ArgumentNullException("valueSelector");
			}
			if (null == values)
			{
				throw new ArgumentNullException("values");
			}

			ParameterExpression p = valueSelector.Parameters.Single();
			// p => valueSelector(p) == values[0] || valueSelector(p) == ...
			if (!values.Any())
			{
				return (e) => false;
			}

			var equals = values.Select(
				value => (Expression)Expression.Equal(
					valueSelector.Body, Expression.Constant(value, typeof(TValue))));

			var body = equals.Aggregate<Expression>(
				(accumulate, equal) => Expression.Or(accumulate, equal));

			return Expression.Lambda<Func<TElement, bool>>(body, p);
		}
	}
}
